Wennekes' template
for the ESP8266

This is a template script for the esp8266.
What are the benefits?

  • Wifi connect by Access Point.
  • Hostname & port adjustable by user.
  • Configuration stored in spiffs memory even after power disconnect.
  • Secured webaccess on browser-cookie level.
  • Example of time display on main webpage in svg format.
  • User can update esp8266 with bin file thru webpage.

First time use.
Connect your esp8266 open your arduino-ide program. Make sure you choose the right board and com port. Open your serial monitor output and set it to 9600 baud. Open the template sketch which you can download from the link on this page. For the first time as the output shows, you will be asked to press the wps button of your router. If everything goes well, the script continues and gives you a ip-adress and/or hostname and port number. Rightclick to see full picture In the meantime the template script stores your wifi credentials in spiffs memory file called wifi.txt which will remain even after power disconnection. Open your browser with http://hostname.port and you will be asked for login credentials. For the first time this will be admin and esp8266 as password. The template script will store now a browser-cookie. This means that if you are using a public computer you must not forget to log out! You are able to change your password and username in the http://hostname/config page. Very important to instruct users to do that.
After logging in the browser-cookie is set to see all pages until you log out. You will be able too see the time on the main page. Rightclick to see full picture On the page you will find a link called settings. You can change the username and password and network settings like hostname and port number. Hostname and portnumber will only applied if the esp8266 restarts. After first time changes in the config-page a file config.txt will be written in the spiffs memory which will be remail even after power disconnecting. I put a function in the script called factorysettings() which will be activated when a button is pressed during the boot process of the esp8266. During this function the wifi.txt en config.txt files will be deleted. Which means that all configuration and wifi settings are lost.
Rightclick to see full picture In the main page you will also the version number. For better administration keep in mind that when you name the sketch that you put the text -version and a version number behind it (ex. dht22-sensor-version1.01).
The version number is also a link to http://hostname/update from which you can update the esp8266 with dismounting the device/esp8266 and without connecting a pc. This is the situation for commercial use when users can download a firmware upgrade form your website. You make this bin file from your arduino-ide.



core.ino

void showWifimanager() {
//if (server.arg("reboot") == "true") {ESP. reset();}
if (server.hasArg("ssid") && server.hasArg("password")){
strSSID = server.arg("ssid");
strPASS = server.arg("password");
Serial.println("New SSID="+strSSID);
Serial.println("New password="+strPASS);
//------------------------------------------------------------------------
File file = SPIFFS.open("/wifi.json", FILE_WRITE);
file.print("{ ");
file.print("\"ssid\":\""+strSSID+"\" , ");
file.print("\"password\":\""+strPASS+"\"");
file.print(" }");
//file.close();
Serial.println(F("Data written to wifi.json."));
Serial.println(F("ESP32 need to be restarted now."));

// !!!!!!! Result page. !!!!!!!!!!
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>Wifimanager</title>\n";
strHTML += "<style>\n";

strHTML += "body {background: #222; color: #EEE; margin: 0px; padding: 1%; font-family: Arial;}\n";
strHTML += "a {background: #333; color: #EEE; display: inline-block;text-decoration: none;text-align: center;\n";
strHTML += "margin: 0px 0px 1% 0px; padding: 20px 0px;font-size: xx-large; width: 100%; height: 40px;}\n";
strHTML += "a:hover {background: orange;}\n";

strHTML += "</style>\n";
strHTML += "</head>\n";
strHTML += "<body>\n";
strHTML += "<center>\n";

strHTML += "wifi.json succesfully written.<br />\n<br />\n";
strHTML += "Restart your device now to connect your network.<br />\n<br />\n";
//strHTML += "<a href=\"?reboot=true\">Reboot</a>\n";
strHTML += "<a href=\"/wifimanager\">WifiManager</a>\n";

strHTML += "</center>\n";
strHTML += "</body>\n</html>";
//strHTML += "</html>";
server.send ( 200, "text/html", strHTML );

} else {

// !!!!!!! Form when no arguments. !!!!!!!!!!
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>Wifimanager</title>\n";

strHTML += "<script>\n";
strHTML += "function togglePassword() {\n";
strHTML += "\tvar x = document.getElementById(\"password\");\n";
strHTML += "\tvar y = document.getElementById(\"peek\");\n";
strHTML += "\tif (x.type === \"password\") {\n";
strHTML += "\t\tx.type = \"text\"; y.value = \"Hide Password\";\n";
strHTML += "\t} else {\n";
strHTML += "\t\tx.type = \"password\"; y.value = \"Show Password\";\n";
strHTML += "\t}\n";
strHTML += "}\n";
strHTML += "</script>\n";

strHTML += "<style>\n";

strHTML += "body {background: #222; color: #EEE; margin: 0px; padding: 1%; font-family: Arial;}\n";
strHTML += "a {background: #333; color: #EEE; display: inline-block;text-decoration: none;text-align: center;\n";
strHTML += "margin: 0px 0px 1% 0px; padding: 20px 0px;font-size: xx-large; width: 100%; height: 40px;}\n";
strHTML += "a:hover, input#save:hover {background: orange;}\n";
strHTML += "label { display: inline-block; width: 20%; height: 40px; background: #555;float: left;text-align: center;\n";
strHTML += "font-size: xx-large; color: #EEE; border: 0px; margin: 0px 0px 1% 0px; padding: 20px 0px;}\n";
strHTML += "input { display: inline-block; width: 79%; height: 40px; background: #555;float: left;text-align: center;\n";
strHTML += "font-size: xx-large; color: #EEE; border: 0px; margin: 0px 0px 1% 0px; padding: 20px 0px; margin-left: 1%;}\n";
strHTML += "input:focus, select:focus {outline: none;}\n";
strHTML += "input#save,input#peek { width: 100%; cursor: pointer; margin: 0px 0px 1% 0px;height: 80px;}\n";

strHTML += "</style>\n";
strHTML += "</head>\n";
strHTML += "<body>\n";

//Scan all AP's on the network
int n = WiFi.scanNetworks();
for (int i = 0; i < n; ++i) {
//strHTML += "\n";
strHTML += "<a href=\"#ssid\" onclick=\"document.getElementById('ssid').value='"+String(WiFi.SSID(i))+"'\">";
strHTML += WiFi.SSID(i);
strHTML += "   (";
strHTML += WiFi.RSSI(i);
strHTML += " dBm)";
strHTML += "</a>\n";
}

strHTML += "<form action='/wifimanager' method='POST'>\n";
strHTML += "<label>SSID </label><input type='text' id='ssid' name='ssid' value=\"" + strSSID + "\" /><br />\n";
strHTML += "<label>Password </label><input type='password' id='password' name='password' value=\"" + strPASS + "\" /><br />\n";
strHTML += "<input id='peek' type='button' onclick='togglePassword()' value='Show Password' />\n";
strHTML += "<input id='save' type='submit' name='submit' value='Save' />\n";
strHTML += "</form>\n";

strHTML += "</body>\n";
strHTML += "</html>";
server.send ( 200, "text/html", strHTML );
}

}


void showMain() {
Serial.print(strTime);
Serial.print(F(" /\t\trequested by "));
Serial.print(server.client().remoteIP());
Serial.print("\t");
if (!is_authentified()){
Serial.println(F("not authentified, redirect to \\login"));
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.send(301);
return;
}
Serial.println(F("authentified"));

String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>" + strHostname + "</title>\n";
strHTML += "</head>\n";
strHTML += "<body onload=\"JavaScript:setTimeout(\'window.location = window.location.pathname;',60000);\">\n";
strHTML += "<svg style=\"background: #333333; width: 100%; height: 100%; position:fixed; top:0; bottom:0; left:0; right:0; border: 5px red;\" viewBox=\"0 0 600 600\">\n";
strHTML += "<circle cx=\"300\" cy=\"300\" r=\"250\" fill=\"none\" stroke= \"orange\" stroke-width=\"30\" />\n";
strHTML += "<text x=\"300\" y=\"300\" text-anchor=\"middle\" dominant-baseline=\"middle\" fill=\"orange\" font-family=\"Arial\" font-size=\"150\" stroke-width=\"1px\">"+strTime.substring(0, 5)+"</text>\n";
strHTML += "<a href= \"login\"><text x=\"0\" y=\"550\" text-anchor=\"left\" fill= \"orange\" font-family=\"Arial\" font-size=\"12\" stroke-width=\"1px\">Logout</text></a>\n";
strHTML += "<a href= \"config\"><text x=\"600\" y=\"550\" text-anchor=\"end\" fill= \"orange\" font-family=\"Arial\" font-size=\"12\" stroke-width=\"1px\">Settings</text></a>\n";
strHTML += "<a href= \"update\"><text x=\"600\" y=\"570\" text-anchor=\"end\" fill= \"orange\" font-family=\"Arial\" font-size=\"12\" stroke-width=\"1px\">v"+strVersion+"</text></a>\n";
strHTML += "</svg>\n";
strHTML += "</body>\n</html>";
//strHTML += "</html>";
server.send ( 200, "text/html", strHTML );
}

void showConfig() {
Serial.print(strTime);
Serial.print(F(" /config\trequested by "));
Serial.print(server.client().remoteIP());
Serial.print("\t");
if (!is_authentified()){
Serial.println(F("not authentified, redirect to \\login"));
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.send(301);
return;
}
Serial.println(F("authentified"));

if (server.hasArg("newhostname") or server.hasArg("newport") or server.hasArg("newusername")){
strHostname = server.arg("newhostname");
strPort = server.arg("newport");
strUser = server.arg("newusername");
if (server.arg("newpassword") != "") {
strPass = server.arg("newpassword"); //if empty keep old password !!!
} else {
Serial.println(F("Password not changed."));
}
if (server.arg("newaptimeout") == "1") {timeout_ap_modus = true;} else {timeout_ap_modus = false;}
Serial.println("New hostname="+strHostname);
Serial.println("New port="+strPort);
Serial.println("New username="+strUser);
Serial.println("New password="+strPass);
Serial.println("New AP after timeout="+String(timeout_ap_modus));
//Store configuration in config.json
//------------------------------------------------------------------------
File file = SPIFFS.open("/config.json", FILE_WRITE);
file.print("{ ");
file.print("\"hostname\":\""+strHostname+"\" , ");
file.print("\"port\":\""+strPort+"\" , ");
file.print("\"user\":\""+strUser+"\" , ");
file.print("\"password\":\""+strPass+"\" , ");
file.print("\"aptimeout\":\""+String(timeout_ap_modus)+"\"");
file.print(" }");
//file.close();
Serial.println(F("Data written to config.json."));
}

if (server.hasArg("remove")) {
//if (SPIFFS.exists("/wifi.txt")) {SPIFFS.remove("/wifi.txt");}
if (SPIFFS.exists(server.arg("remove"))) {SPIFFS.remove(server.arg("remove"));}
Serial.print(server.arg("remove").substring(1));
Serial.println(F(" has been removed from the filesystem."));
}

if (server.hasArg("download")) {
if (SPIFFS.exists(server.arg("download"))) {
Serial.print(F("Downloading: "));
Serial.println(server.arg("download").substring(1));
File file = SPIFFS.open(server.arg("download"), "r");
String strContent;
while (file.available()) {
strContent += file.readStringUntil('\n');
}
//file.close();
server.sendHeader("Content-Disposition", "attachment; filename="+server.arg("download").substring(1));
server.send(200, "application/octet-stream", strContent );
}
}

String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>" + strHostname + "</title>\n";
//strHTML += strDefaulttheme + "\n"; //Inline css
strHTML += "<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />\n";
strHTML += "</head>\n";
strHTML += "<body>\n";

strHTML += "<center>\n";
strHTML += "<a href=\"/\">Home</a>\n";
strHTML += "<a href=\"/config\">Settings</a>\n";
strHTML += "<a href=\"/update\">Update</a>\n";
strHTML += "<a href=\"/wifimanager\">Wifimanager</a>\n";
strHTML += "</center>\n";
strHTML += "<br />\n";

strHTML += "<form action='/config' method='POST'>\n";
strHTML += "<center>\n";
strHTML += "<label>Hostname</label><input type='text' name='newhostname' value=\"" + strHostname + "\" /><br />\n";
strHTML += "<label>Port</label><input type='text' name='newport' value=\"" + strPort + "\" /><br />\n";
strHTML += "<label>Username</label><input type='text' name='newusername' value=\"" + strUser + "\" /><br />\n";
strHTML += "<label>Password</label><input type='password' name='newpassword' value=\"\" /><br />\n";

strHTML += "<label class=\"checkbox\">AP after wifi timeout</label><input type='checkbox' name='newaptimeout' value=\"1\" ";
if (timeout_ap_modus) {strHTML += "checked=\"checked\" ";}
strHTML += "/><br />\n";

strHTML += "<br />\n";


//strHTML += "<span style=\"font-size: 10pt;\">*** The Hostname & Port only applied after restart. ***</span><br /><br />\n";
strHTML += "<button type='submit' name='submit'>Save Configuration</button><br /><br />\n";
strHTML += "</center>\n";
strHTML += "</form>\n";

strHTML += "\n\n";
strHTML += "<script>function updateFilenameValue(val) {val=val.replace(\"C:\\\\fakepath\\\\\", \"\");document.getElementById('fuplo2').innerHTML=val;}</script>\n";
strHTML += "<form method='POST' action='/uploadfile' enctype='multipart/form-data'>\n";
strHTML += "<center>\n";
strHTML += "<label for='fuplo' id='fuplo2' style='cursor: pointer; width: 640px;'>Choose file</label><input id='fuplo' type='file' style='display: none;' name='upload' oninput='updateFilenameValue(this.value);' /><br />\n";
strHTML += "<button type='submit' name='upload'>Upload</button><br /><br />\n";
strHTML += "</center>\n";
strHTML += "</form>\n\n";

strHTML += "<center>\n\n";

//Display directory of filesystem
File root = SPIFFS.open("/");
File file = root.openNextFile();
while(file){
//Serial.print(dir.fileName());
strHTML += "<a style='width: 462px;' href=\"?download=";
strHTML += String(file.name());
strHTML += "\">";
strHTML += String(file.name()).substring(1);
strHTML += " (";
strHTML += String(file.size());
strHTML += " bytes)</a>\n";

strHTML += "<a href=\"?remove=";
strHTML += String(file.name());
strHTML += "\" onclick=\"return confirm('Are you absolutely sure?');\">Remove</a><br />\n\n";
file = root.openNextFile();
}
strHTML += "\n";
strHTML += "</center>\n\n";


strHTML += "</body>\n</html>";
//strHTML += "</html>";
server.send ( 200, "text/html", strHTML );
}

void showLogin() {
Serial.print(strTime);
Serial.print(F(" /login\trequested by "));
Serial.print(server.client().remoteIP());
Serial.print("\t");
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>" + strHostname + "</title>\n";
//strHTML += strDefaulttheme + "\n"; //Inline css
strHTML += "<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />\n";
strHTML += "</head>\n";
strHTML += "<body>\n";
//**** Login ****
if (server.hasArg("login")){
if (server.arg("user") == strUser && server.arg("pass") == strPass){
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.sendHeader("Set-Cookie","authlevel=1"); //Set browsercookie to 1
server.send(301);
Serial.print(F("Login succeeded "));
strHTML += "<center>\n";
strHTML += "Username and Password are correct<br /><br />\n";
strHTML += "</center>\n";
} else {
Serial.print(F("Login failed, try again "));
strHTML += "<center>\n";
strHTML += "Wrong username or password<br /><br />\n";
strHTML += "</center>\n";
}
}
//**** Logout ****
if (server.hasArg("logout")){
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.sendHeader("Set-Cookie","authlevel=0"); //Set browsercookie to 0
server.send(301);
Serial.print(F("logged out succesfully "));
}
if (!is_authentified()){
Serial.println(F("not authentified"));
//********** login form & button ************
strHTML += "<form action='/login' method='POST'>\n";
strHTML += "<center>\n";
strHTML += "<input type='text' name='user' placeholder='User name' /><br />\n";
strHTML += "<input type='password' name='pass' placeholder='Password' /><br /><br />\n";
strHTML += "<button type='submit' name='login'>login</button>\n";
strHTML += "</center>\n";
strHTML += "</form>\n";
} else {
Serial.println(F("authentified"));
//*********** logout button *************
strHTML += "<form action='/login' method='POST'>\n";
strHTML += "<center>\n";
strHTML += "You are succesfully logged in<br /><br />\n";
strHTML += "<a href=\"/\">Main panel</a><br /><br />\n";
strHTML += "<a href=\"config\">Settings</a><br /><br />\n";
strHTML += "<button type='submit' name='logout'>logout</button>\n";
strHTML += "</center>\n";
strHTML += "</form>\n";
}

strHTML += "</body>\n</html>";
server.send ( 200, "text/html", strHTML );
}


int ntpSync()
{
//get a random server from the pool
WiFi.hostByName(ntpServerName, timeServerIP);

sendNTPpacket(timeServerIP); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);

int cb = udp.parsePacket();
if (cb) {
//Serial.print("packet received, length=");
//Serial.println(cb);
// We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:

unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
//Serial.print("Seconds since Jan 1 1900 = " );
//Serial.println(secsSince1900);

// now convert NTP time into everyday time:
//Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
// print Unix time:
//Serial.println(epoch);
return epoch;
} else {
//Serial.println("no packet yet");
return false;
}
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
//Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;

// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}


long adjustDstEurope(long epoch)
{
// last sunday of march
int beginDSTDate= (31 - (5* year(epoch) /4 + 4) % 7);
int beginDSTMonth=3;
//last sunday of october
int endDSTDate= (31 - (5 * year(epoch) /4 + 1) % 7);
int endDSTMonth=10;
// DST is valid as:
if (((month(epoch) > beginDSTMonth) && (month(epoch) < endDSTMonth))
|| ((month(epoch) == beginDSTMonth) && (day(epoch) >= beginDSTDate))
|| ((month(epoch) == endDSTMonth) && (day(epoch) < endDSTDate)))
return true; // DST europe = GMT +2
else return false; // nonDST europe = GMT +1
}


String httprequest(String urlrequest) {
String payload = "";
http.begin(urlrequest); //Specify request destination
int httpCode = http.GET(); //Send the request
//Serial.println(httpCode);

if (httpCode > 0) { //Check the returning code
payload = http.getString(); //Get the request response payload
//Serial.println(payload); //Print the response payload
}
http.end(); //Close connection
return payload;
} // EOF httprequest()


void factory_reset() {
Serial.println(F("Factory reset start in 5 seconds ..."));
for (int i = 0; i < 5; i++) {
Serial.print("*");
//display output device
delay(1000);
}
Serial.println();
//***** Actually clearing *****
if (SPIFFS.exists("/config.json")) {SPIFFS.remove("/config.json");}
if (SPIFFS.exists("/wifi.json")) {SPIFFS.remove("/wifi.json");}

while (digitalRead(setbuttonPin) == HIGH) {
//Do nothing until button is released.
}
delay(5000);
} // EOF factory_reset()


bool is_authentified() {
if (server.hasHeader("Cookie")){
//Serial.print("Found cookie: ");
strCookie = server.header("Cookie");
//Serial.println(strCookie);
if (strCookie.indexOf("authlevel=1") != -1) {return true;} else {return false;}
}
} // EOF is_authentified()


void handleFileUpload() {
HTTPUpload& upload = server.upload();
String filename = upload.filename;
int filesize = upload.totalSize;
Serial.print(F("FileUpload name: ")); Serial.println(filename);
Serial.print(F("FileUpload size: ")); Serial.println(filesize);
if (upload.status == UPLOAD_FILE_START){
Serial.println(F("UPLOAD_FILE_START"));
fsUploadFile = SPIFFS.open("/" + filename, "w");
filename = String();
}
else if (upload.status == UPLOAD_FILE_WRITE){
Serial.println(F("UPLOAD_FILE_WRITE"));
Serial.print(F("handleFileUpload Data: "));
Serial.println(upload.currentSize);
if(fsUploadFile) {
fsUploadFile.write(upload.buf, upload.currentSize);
Serial.println(F("WRITING BUFFER TO SPIFFS"));
}
}
else if (upload.status == UPLOAD_FILE_END){
Serial.println(F("UPLOAD_FILE_END"));
if (fsUploadFile) {
fsUploadFile.close();
Serial.println(F("CLOSE & OK"));
Serial.println(filename);
}
Serial.print(F("handleFileUpload Size: "));
Serial.println(upload.totalSize);
}
} //EOF HandleFileUpload

template-esp32-version0.68.ino

// *****************************************************************
// * Wennekes' Template *
// * for ESP32 *
// * *
// * # Wifi connected once accespoint. *
// * # Hostname & port adjustable *
// * # All configurations save on spiffs. *
// * # Secured web acces based on cookie-level. *
// * # Example of svg webpage showing current time . *
// * # Update esp32 by upload bin file without *
// * dismounting esp32 just thru webpage. *
// * *
// * This script is free of use *
// * if you find this script useful *
// * consider to buy me a cup of coffee *
// * see http://www.wennekes.info *
// * or like my video on *
// * https://www.youtube.com/c/ReneWennekes *
// *****************************************************************


#include <Wire.h>
#include "TimeLib.h"
#include "WiFi.h"
#include "esp_wps.h"
#include "esp_timer.h"
#include <WiFiClient.h> //#include <ESP8266WiFi.h>
//#include <WiFiClientSecure.h> //********* https
#include <HTTPClient.h> //#include <ESP8266HTTPClient.h>
#include <WebServer.h> //#include <ESP8266WebServer.h>
#include <ESPmDNS.h> //#include <ESP8266mDNS.h>
#include <Update.h> //#include <WiFiUdp.h>
#include "FS.h"
#include "SPIFFS.h"

uint8_t setbuttonPin = 2; // D4 of ESP8266 (Button)

/* You only need to format SPIFFS the first time you run a
test or else use the SPIFFS plugin to create a partition
https://github.com/me-no-dev/arduino-esp32fs-plugin */
#define FORMAT_SPIFFS_IF_FAILED true

//#define LED_MONITORING LED_BUILTIN
#define LED_MONITORING 13

// Determine version for administration, display it on a webpage and/or output device during boot.
// Use always this format: <your projectname>version<version>
// Increase version each time you update the sketch
String strFilepath =__FILE__;
String strVersion = strFilepath.substring(strFilepath.lastIndexOf("version")+7, strFilepath.lastIndexOf(".ino"));

// ********** wifi ************
String strSSID;
String strPASS;
const char* SSIDAP = "ESP32-template"; //SSID name of access-point.
String strMac;
boolean timeout_ap_modus = true; //if timeout is reached goto acces point modus.
//use false (restart) in case essential device in absense of supervising.

// ********** webserver ************
//String strChipID = String(ESP.getChipId(), HEX); //Unique number of each esp8266 device
uint64_t mac = ESP.getEfuseMac();
unsigned long long1 = (unsigned long)((mac & 0xFFFF0000) >> 16 );
unsigned long long2 = (unsigned long)((mac & 0x0000FFFF));
String strChipID = String(long1, HEX) + String(long2, HEX); // six octets
//String strChipID = "000000";
String strHostname = "esp32-" + strChipID; //default
uint16_t port = 80; //default
String strPort = String(port);
// CSS style (themes) example
//String strDefaulttheme = "";
String strDefaulttheme = \
"body {background: #333 url(\"background.jpg\") no-repeat center top; color: #EEE; margin: 20px; font-family: Arial;}\n" \
"a {background: #555; color: #EEE; display: inline-block;text-decoration: none; width: 128px; height: 20px;\n" \
"margin: 2px 0px; padding: 20px;box-shadow: 10px 10px 5px black;}\n" \
"a:hover, button:hover {background: orange;}\n" \
"label, input, button, select {vertical-align: middle; display: inline-block;\n" \
"width: 400px; height: 32px; margin: 2px; padding: 0px 20px;\n" \
"font-size: 16pt; color: #EEE; border: 0px; background: #555;box-shadow: 10px 10px 5px black;}\n" \
"input:focus, select:focus {outline: none; background: chocolate;}\n" \
"label.checkbox {width: 608px;}" \
"input[type=\"checkbox\"] {width: 32px;}" \
"label {width: 200px;}\n" \
"select {width: 440px; margin-left: -3px;-webkit-appearance: none;-moz-appearance: none;text-indent: 1px;text-overflow: '';}\n" \
"button {width: 680px; cursor: pointer;height: 80px;}\n";


String strFavicon = "";
//String strFavicon = "<link rel=\"shortcut icon\" href=\"data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAAAAAxTTH/LVAt/y9RL/8uUS//LlAu/y9RL/8uUC7/L1Av/y9QL/8uTy7/L1Av/y5PLv8uTy7/L1Av/y5PLv8xUDH/ODU4/zUxNf82NDj/NjI1/zUyNf83NDf/NTI1/zYzNv83Mzf/NTE1/zczN/82Mjb/NjI2/zczN/81MTX/ODQ4/zU2Nf8zNTX/NjIu/zM3N/80NDP/NDU0/zQ1NP80NTT/NDU0/zQ1NP80NTT/NDU0/zM0M/81NjX/NDQ0/zU2Nf8zNjf/Niwm/yNWcf8EnO7/LzxD/zQwLv8zMzT/MzMz/zMzM/8zMjL/MzMz/zMzM/8kJCT/Kysr/zQ0NP80NDT/NjIv/y87Q/8Cp/n/FHyv/zUvLP8yMzT/MzMz/zMyMv80Nzj/Lj9I/zIzNP80NDP/Kysr/yAgIP8wMDD/NTU1/zkqJP8XdKL/BJ/u/zQwL/80MTD/NTY3/zU1Nf83Nzf/OTk5/zQ2N/83Nzf/NDQ0/zQ0NP8lJSb/Jico/zU2Nv81MzH/Bpvo/xptmv83KiP/MjY5/zo7O/85Ojr/Ojo7/z4/P/86Ozv/Ozs7/zQ1Nv80NDX/Li4t/yMcGP8zMjH/L0BI/wGk+/8lU2z/Nyki/zQxMP80MC7/NTIw/zQxMP80MjH/NTAu/zM0NP80MC3/NTAu/zExMv8bPlD/LjY6/y5ET/8Apf3/KEte/y5ASP8rRlT/LENP/y4+Rv8wOkD/MDpA/yxEUf81Lyv/K0ZU/y5AS/8vO0D/Aab4/yZUbP8yOj7/AqL3/yJZdv8tQUr/Il1+/yFfgf8uQEr/KUxe/yZUbf8jWnn/LUNP/yVWcv8wPET/KUpb/wCm/f8rSln/OC0o/wyNzv8Qg77/MjY4/ydRZ/8nUGb/MzQ1/ylMXf8mU2v/Kkpc/ytIWv8nT2X/MDpB/xptmP8FnOr/NTQz/zgtJ/8jV3H/ALD//ypIWf84Jx7/MzMy/zI0Nf80MTD/NS8s/zQxMP8zMzT/Nism/zA5Pv8Bqfz/G2qT/zgrJP80NTb/NS0p/xCFv/8DpvT/LEJP/zgpIf80MC7/MzMz/zM0NP80MTD/Nyoj/zE3PP8ImN7/CZfc/zMyM/81NDP/NDU2/zMxMP8zNDX/DYzM/wCt//8abJj/LkBJ/zMzMv8zMjH/LzxC/x5hhv8Cpfb/B5rj/y88RP80Ly3/NDY2/zQ0NP8yMzT/NDEw/zQwLf8baZP/AqH2/wCm/v8Em+3/BZrr/wGk+/8BpPz/FXSn/zI1Nv80Ly3/MjQ1/zQ0NP82Njb/NDQ0/zQ1Nv80NDT/OCsk/zM3Of8mVnD/HGuV/xtsmP8jWnf/MTxB/zgrJP81MzL/NDY3/zQ0NP81Njb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\" />";

File fsUploadFile;
String strCookie;

// ********** security ************
String strUser = "admin"; //default username
String strPass = "esp8266"; //default password

//Set to European (if other region disable or adjust settings in adjustDstEurope() function in case summer/wintertime)
uint16_t timeZone = 7200;
boolean lastsync = false;
uint64_t elapsedTime;
String strTime;
String strDate;

unsigned int localPort = 2390; // local port to listen for UDP packets

/* Don't hardwire the IP address or we won't get the benefits of the pool.
* Lookup the IP address for the host name instead */
//IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP; // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
//Update udp;

//Webserver
WebServer server(port);

//WiFiClient client
HTTPClient http;


void setup() {
delay(2000); //prevents that windows detect ballpoint serial mouse
//you may connect a 10k resistor to pin15 and gnd to supress bootloader message of 115000 baud

Serial.begin(9600);
Serial.println();

//Enable he software watchdog
//ESP.wdtEnable(60000); //Not working on ESP32


// initialize the pushbutton pins as an input:
pinMode(setbuttonPin, INPUT);

// initialize the led pins as an output:
//pinMode(LED_BUILTIN, OUTPUT);
pinMode(LED_MONITORING, OUTPUT);

//Display firmware version
//Consider display version briefly on output device during startup
//and one or more webpages.
Serial.println("\nFirmware Version: " + strVersion);

//Display Chip ID
Serial.println("Chip ID: " + strChipID);
//Serial.println(ESP.getChipId());

//Initialize File System
if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
Serial.println(F("SPIFFS Mount Failed"));
} else {
Serial.println(F("SPIFFS Initialize....ok"));
}

//Display directory of filesystem
File root = SPIFFS.open("/");
File file = root.openNextFile();
Serial.println(F("FILE:\tSIZE:"));
while(file){
Serial.print(file.name());
Serial.print("\t");
Serial.println(file.size());
file = root.openNextFile();
}


//Press the button to erase config files
//if (digitalRead(setbuttonPin) == HIGH) {factory_reset();}
//factory_reset();


//Restore important vars from config.json
//------------------------------------------------------------------------
if (SPIFFS.exists("/config.json")) {
file = SPIFFS.open("/config.json");
while(file.available()){
String strLine = file.readStringUntil(','); //strLine.trim();
strLine.replace("{", "");
strLine.replace("}", "");
strLine.trim();
//String strKey = strLine.substring(1, strLine.indexOf(":")-1);
String strKey = strLine.substring(0, strLine.indexOf(":")); strKey.replace("\"", "");
String strVal = strLine.substring(strLine.indexOf(":")+1); strVal.replace("\"", "");
if (strKey == "hostname") {strHostname = strVal;}
if (strKey == "port") {strPort = strVal;}
if (strKey == "user") {strUser = strVal;}
if (strKey == "password") {strPass = strVal;}
if (strKey == "aptimeout") {if (strVal == "1") {timeout_ap_modus = true;} else if (strVal == "0") {timeout_ap_modus = false;}}
}
Serial.print(F("Hostname="));Serial.println(strHostname);
Serial.print(F("Port="));Serial.println(strPort);
Serial.print(F("User="));Serial.println(strUser);
Serial.print(F("Pass="));Serial.println(strPass);
Serial.print(F("AP after timeout="));Serial.println(String(timeout_ap_modus));
Serial.println(F("Data read from config.json."));
}


//Restore SSID & WPA2 password from wifi.json
// but is the setbutton is pressed we go to wps/ap modus.
//------------------------------------------------------------------------
//if (SPIFFS.exists("/wifi.json") and digitalRead(setbuttonPin) != HIGH) {
if (SPIFFS.exists("/wifi.json")) {
file = SPIFFS.open("/wifi.json");
while(file.available()){
String strLine = file.readStringUntil(','); //strLine.trim();
strLine.replace("{", "");
strLine.replace("}", "");
strLine.trim();
//String strKey = strLine.substring(1, strLine.indexOf(":")-1);
String strKey = strLine.substring(0, strLine.indexOf(":")); strKey.replace("\"", "");
String strVal = strLine.substring(strLine.indexOf(":")+1); strVal.replace("\"", "");
if (strKey == "ssid") {strSSID = strVal;}
if (strKey == "password") {strPASS = strVal;}
}
Serial.print(F("ssid="));Serial.println(strSSID);
Serial.print(F("password="));Serial.println(strPASS);
Serial.println(F("Data read from wifi.json."));


// ********************** Wifi ***********************
//Convert String to char
char charSSID[strSSID.length()];
char charPASS[strPASS.length()];
char charHOST[strHostname.length()];
strSSID.toCharArray(charSSID, strSSID.length()+1);
strPASS.toCharArray(charPASS, strPASS.length()+1);
strHostname.toCharArray(charHOST, strHostname.length()+1);
WiFi.setHostname(charHOST);
//WiFi.hostname(strHostname);
WiFi.begin(charSSID, charPASS);
WiFi.mode(WIFI_STA);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
//if (digitalRead(setbuttonPin) == HIGH) {factory_reset();}
// !!!! Choose if a module must be restarted (essential by absense) or get in ap modus after x seonds ....
//if (minute() > 2) {ESP.restart();} //Try for 3 minutes to connect otherwise restart....
//if (minute() > 2) {goto apmodus;} //Try for 3 minutes to connect otherwise go in AP modus....
if (minute() > 2) {
if (timeout_ap_modus) {goto apmodus;} else {ESP.restart();}
}
}
strMac = WiFi.BSSIDstr();
Serial.println("");
Serial.println(F("Connecting to:"));
Serial.println(strSSID);
Serial.println(strPASS);
Serial.println(F("WiFi connected"));

} else {

apmodus:
//-----------------Retrieve credentials by AP-----------------------------
//------------------------------------------------------------------------
Serial.println(F("wifi.json not exist."));
Serial.println(F("Start now in AP modus."));
Serial.print(F("SSID: ")); Serial.println(SSIDAP);
//WiFi.softAPConfig(IPAddress(192, 168, 177, 10, IPAddress(255, 255, 255, 0, IPAddress(255, 255, 255, 0));
WiFi.softAP(SSIDAP);
WiFi.mode(WIFI_AP);
Serial.println(WiFi.softAPIP());

server.on ( "/wifimanager", showWifimanager );
server.on ( "/", showWifimanager );
server.onNotFound( showWifimanager );

Serial.println(F("Starting webserver"));
server.begin();
MDNS.addService("http", "tcp", 80);

// Check if a client has connected
while (true) {server.handleClient();}
}


server.on ( "/", showMain );
server.on ( "/config", showConfig );
server.on ( "/wifimanager", showWifimanager );
server.on ( "/login", showLogin );
server.on ( "/style.css", []() { server.send ( 200, "text/css", strDefaulttheme ); } ); //strDefaulttheme
//server.on ( "/config.json", []() {
// File file = SPIFFS.open("/config.json", "r");
// size_t sent = server.streamFile(file, "application/octet-stream;");
// file.close();
// } );
/*server.on ( "/config.json", []() {
//----------0.77----------------------------------------------------------------
Serial.print(strTime);
Serial.print(F(" /config.json\trequested by "));
Serial.print(server.client().remoteIP());
Serial.print("\t");
if (!is_authentified()){
Serial.println(F("not authentified, redirect to login"));
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.send(301);
return;
}
Serial.println(F("authentified"));
//------------------------------------------------------------------------------
File file = SPIFFS.open("/config.json", "r");
size_t sent = server.streamFile(file, "application/octet-stream;");
file.close();
} );*/


server.on("/uploadfile", HTTP_POST,[](){
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>Solaredge - " + strHostname + "</title>\n";
//strHTML += strDefaulttheme + "\n"; //Inline css
strHTML += "<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />\n";
strHTML += "</head>\n";
strHTML += "<body>\n";
strHTML += "<br /><br /><br /><br /><br /><br /><center>File uploaded.</center>\n";
strHTML += "<br /><br /><center>";
strHTML += "<a href=\"/config\">Back to settings</a><br /><br />\n";
strHTML += "<a href=\"/\">Back to panel</a>\n";
strHTML += "</center>\n\n";
strHTML += "</body>\n</html>";
server.send(200, "text/html", strHTML);
}, handleFileUpload);


//********* webform firmware upload ************************
server.on("/update", HTTP_GET, [](){
Serial.print(strTime);
Serial.print(F(" /update\trequested by "));
Serial.print(server.client().remoteIP());
Serial.print("\t");
if (!is_authentified()){
Serial.println(F("not authentified, redirect to \\login"));
server.sendHeader("Location","/login");
server.sendHeader("Cache-Control","no-cache");
server.send(301);
return;
}
Serial.println(F("authentified"));
server.sendHeader("Connection", "close");
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>" + strHostname + "</title>\n";
//strHTML += strDefaulttheme + "\n"; //Inline css
strHTML += "<link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\" />\n";
strHTML += strFavicon + "\n"; //Favicon
strHTML += "</head>\n";
strHTML += "<body>\n";
strHTML += "<form method='POST' action='/flash' enctype='multipart/form-data'>\n";
strHTML += "<center>\n";
strHTML += "<input type='file' name='update'><br />\n";
strHTML += "Current firmware: " + strVersion + "<br /><br />\n";
//strHTML += "<input type='submit' value='Update'>\n";
strHTML += "<button type='submit' name='Update'>Update</button><br /><br />\n";
strHTML += "</center>\n";
strHTML += "</form>\n";
strHTML += "<center>\n";
strHTML += "<a href=\"/\">Back to panel</a>\n";
strHTML += "</center>\n";
strHTML += "</body>\n</html>";
server.send(200, "text/html", strHTML);
});

//********* upload the flash file ***************************
server.on("/flash", HTTP_POST, [](){
server.sendHeader("Connection", "close");
String strHTML;
strHTML += "<!DOCTYPE HTML>\n<html>\n<head>\n";
strHTML += "<title>OTA Result</title>\n";
strHTML += "</head>\n<body style=\"background: #333; color: #EEE;\">\n";
if (Update.hasError()){
strHTML += "<strong>OTA Failed.</strong>\n";
} else {
strHTML += "<strong>OTA update succeeded.</strong><br />\n";
strHTML += "Wait approx. 60sec.<br />\n";
}
strHTML += "<a style=\"color: #EEE;\" href=\"/\">Home</a>\n";
strHTML += "</body>\n</html>";
server.send(200, "text/html", strHTML);
delay(5000); //Give time to send page to client ota is ready.
ESP.restart();
},[](){
HTTPUpload& upload = server.upload();
if(upload.status == UPLOAD_FILE_START){
Serial.setDebugOutput(true);
////////////////WiFiUDP::stopAll();
Serial.printf("Update: %s\n", upload.filename.c_str());
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
if(!Update.begin(maxSketchSpace)){//start with max available size
Update.printError(Serial);
}
} else if(upload.status == UPLOAD_FILE_WRITE){
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
Update.printError(Serial);
}
} else if(upload.status == UPLOAD_FILE_END){
if(Update.end(true)){ //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); //
} else {
Update.printError(Serial);
}
Serial.setDebugOutput(false);
}
//******************************************************************************************
yield();
});

server.on ( "/inline", []() {
server.send ( 200, "text/plain", "this works as well" );
} );

server.on ( "/favicon.ico", []() {
File file = SPIFFS.open("/favicon.ico", "r");
size_t sent = server.streamFile(file, "image/x-icon");
file.close();
} );

//server.onNotFound ( showNotFound );

//!!!!!!!These lines are important to work with cookies !!!!!!!!!!
//here the list of headers to be recorded
const char * headerkeys[] = {"User-Agent","Cookie"} ;
size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);
//ask server to track these headers
server.collectHeaders(headerkeys, headerkeyssize );


char host[strHostname.length()];
strHostname.toCharArray(host, strHostname.length()+1);
port = strPort.toInt();

// Start the server
Serial.println(F("Starting webserver"));
MDNS.begin(host);
server.begin();
MDNS.addService("http", "tcp", port);
Serial.print(F("http://"));
Serial.print(WiFi.localIP()); //Consider to display ip on output device.
Serial.print(":");
Serial.println(port); //Consider to display ip on output device.

Serial.println(F("Starting UDP"));
udp.begin(localPort);
Serial.print(F("Local port: "));
Serial.println(localPort);
//Serial.println(udp.localPort());

/*while (year()<2000){
if (long t = ntpSync()){
if (adjustDstEurope(t)) {timeZone = 7200;} else {timeZone = 3600;}
if (t) {setTime(t+timeZone);}
//Serial.println(t);
Serial.println(F("Synced now."));
lastsync = true;
} else {
Serial.println(F("Synced failed."));
lastsync = false;
delay(1000);
}
}*/


} //EOF setup


void loop() {
//sync once a hour on xx:00:25
if ((minute() == 0 && second() == 25) or (!lastsync && second() == 25)) {
if (long t = ntpSync()){
if (adjustDstEurope(t)) {timeZone = 7200;} else {timeZone = 3600;}
if (t) {setTime(t+timeZone);}
//Serial.println(t);
Serial.println(F("Synced now."));
lastsync = true;
} else {
Serial.println(F("Synced failed."));
lastsync = false;
}
}

//uptime
elapsedTime = esp_timer_get_time(); //uint64_t
uint32_t uptime = ((elapsedTime % 0xFFFFFFFF) / 1000000) + ((elapsedTime >> 32) % 0xFFFFFFFF) * ((256*256*256*256)/1000000);

unsigned long Hour = hour();
unsigned long Minute = minute();
unsigned long Second = second();
unsigned long Year = year();
unsigned long Month = month();
unsigned long Day = day();

int hour1 = (Hour%10);
int hour10 = (Hour/10);
int minute1 = (Minute%10);
int minute10 = (Minute/10);
int second1 = (Second%10);
int second10 = (Second/10);
int year1 = (Year%10);
int year10 = ((Year/10)%10);
int year100 = ((Year/100)%10);
int year1000 = (Year/1000);
int month1 = (Month%10);
int month10 = (Month/10);
int day1 = (Day%10);
int day10 = (Day/10);

strTime = String(hour10, DEC)+String(hour1, DEC)+":"+String(minute10, DEC)+String(minute1, DEC)+":"+String(second10, DEC)+String(second1, DEC); //for serial output log
strDate = String(day10, DEC)+String(day1, DEC)+"-"+String(month10, DEC)+String(month1, DEC)+"-"+String(year1000, DEC)+String(year100, DEC)+String(year10, DEC)+String(year1, DEC);


//Webserver
//-----------------------------------------------------------------
// Check if a client has connected
server.handleClient();


//digitalWrite(LED_BUILTIN, LOW);
//digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); //Toggle led each loop for monitoring your project.
digitalWrite(LED_MONITORING, !digitalRead(LED_MONITORING )); //Toggle led each loop for monitoring your project.


delay(100);
} //EOF loop

Example of the serial output

Firmware Version: 0.46
Chip ID: 877e71
SPIFFS Initialize....ok
/wifi.txt 30
/config.txt 27
Hostname=clock
Port=80
User=admin
Pass=esp8266
Data read from config.txt.
SSID=XXXXXXXXXX
PASS=YYYYYYYYYY
Data read from wifi.txt.
.
Connecting to:
XXXXXXXXXX
YYYYYYYYYY
WiFi connected
Starting webserver
http://192.168.0.157:80
Starting UDP
Local port: 2390
Synced now.
08:20:40 /config requested by 192.168.0.117 authentified
08:20:43 / requested by 192.168.0.117 authentified



Download template-esp32-version0.68.rar and extract the zip in your sketches folder.

Buy me a cup of coffee

If you like this project, keep in mind that a huge amount of coffee was consumed in the process of making this script. Please donate a cup of coffee if you want me to keep going.